package com.pdg.dungeondelver.defaulttheme.builders;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.pdg.dungeondelver.client.GeneratorConstants;
import com.pdg.dungeondelver.client.constants.BuilderConstants;
import com.pdg.dungeondelver.client.constants.GameConstants;
import com.pdg.dungeondelver.client.constants.PropertyGroupConstants;
import com.pdg.dungeondelver.client.constants.TerrainConstants;
import com.pdg.dungeondelver.client.constants.UtilitiesConstants;
import com.pdg.dungeondelver.configuration.CQMFile;
import com.pdg.dungeondelver.configuration.Configuration;
import com.pdg.dungeondelver.configuration.Descriptor;
import com.pdg.dungeondelver.configuration.DescriptorTable;
import com.pdg.dungeondelver.configuration.MapManager;
import com.pdg.dungeondelver.engine.Atlas;
import com.pdg.dungeondelver.engine.AtlasBuilder;
import com.pdg.dungeondelver.engine.AtlasCell;
import com.pdg.dungeondelver.engine.AtlasColumn;
import com.pdg.dungeondelver.engine.AtlasColumnBuilder;
import com.pdg.dungeondelver.engine.AtlasDirections;
import com.pdg.dungeondelver.engine.BaseInstance;
import com.pdg.dungeondelver.engine.BaseTerrainInstance;
import com.pdg.dungeondelver.engine.Layer;
import com.pdg.dungeondelver.engine.RandomNumberGenerator;
import com.pdg.dungeondelver.engine.WeightedGenerator;

public class DefaultAtlasBuilder implements AtlasBuilder{

	private static final String MAP_CHAMBER_DEAD_END = "chamber-dead-end-floor";
	private static final String MAP_CHAMBER_BASE = "chamber-base";
	private static final String MAP_CHAMBER_DOOR_NORTH = "chamber-door-north";
	private static final String MAP_CHAMBER_OPEN_NORTH = "chamber-open-north";
	private static final String MAP_CHAMBER_DOOR_EAST = "chamber-door-east";
	private static final String MAP_CHAMBER_OPEN_EAST = "chamber-open-east";
	private static final String MAP_CHAMBER_DOOR_SOUTH = "chamber-door-south";
	private static final String MAP_CHAMBER_OPEN_SOUTH = "chamber-open-south";
	private static final String MAP_CHAMBER_DOOR_WEST = "chamber-door-west";
	private static final String MAP_CHAMBER_OPEN_WEST = "chamber-open-west";
	private static final String MAP_PWAY_DOOR_NORTH = "pway-door-north";
	private static final String MAP_PWAY_DOOR_EAST = "pway-door-east";
	private static final String MAP_PWAY_DOOR_SOUTH = "pway-door-south";
	private static final String MAP_PWAY_DOOR_WEST = "pway-door-west";
	private static final String MAP_PWAY_FORMAT = "pway-%x";

	public DefaultAtlasBuilder() {
	}
	
	private void populateNeighbors(Atlas theAtlas){
		Iterator<Integer> columnIterator = theAtlas.keySet().iterator();
		while(columnIterator.hasNext()){
			Integer currentColumn = columnIterator.next();
			Iterator<Integer> rowIterator = theAtlas.get(currentColumn).keySet().iterator();
			while(rowIterator.hasNext()){
				Integer currentRow = rowIterator.next();
				AtlasCell currentCell = theAtlas.get(currentColumn).get(currentRow);
				for(AtlasDirections direction:AtlasDirections.getDirections()){
					Integer nextColumn = direction.getNextColumn(currentColumn, currentRow);
					Integer nextRow = direction.getNextRow(currentColumn,currentRow);
					AtlasColumn nextAtlasColumn = theAtlas.get(nextColumn);
					if(nextAtlasColumn!=null){
						AtlasCell nextAtlasCell = nextAtlasColumn.get(nextRow);
						if(nextAtlasCell!=null){
							currentCell.get(0).setNeighbor(direction, nextAtlasCell.get(0));
						}
					}
				}
			}
		}
	}
	
	private void generateMaze(Atlas theAtlas){
		RandomNumberGenerator rng = theAtlas.getConfiguration().getPropertyGroups().get(PropertyGroupConstants.UTILITIES).<RandomNumberGenerator>get(UtilitiesConstants.RANDOM_NUMBER_GENERATOR);
		Integer column = (Integer)theAtlas.keySet().toArray()[rng.next(theAtlas.keySet().size())];
		Integer row = (Integer)theAtlas.get(column).keySet().toArray()[rng.next(theAtlas.get(column).keySet().size())];
		Set<Layer> frontier = new HashSet<Layer>();
		Set<Layer> inside = new HashSet<Layer>();
		Layer cell = theAtlas.get(column).get(row).get(0);
		inside.add(cell);
		for(AtlasDirections direction:AtlasDirections.getDirections()){
			if(cell.getNeighbor(direction)!=null){
				frontier.add(cell.getNeighbor(direction));
			}
		}
		while(!frontier.isEmpty()){
			cell = (Layer)frontier.toArray()[rng.next(frontier.size())];
			frontier.remove(cell);
			inside.add(cell);
			Set<AtlasDirections> directions = new HashSet<AtlasDirections>();
			Iterator<AtlasDirections> iterator = cell.getNeighborDirectionsIterator();
			while(iterator.hasNext()){
				AtlasDirections direction = iterator.next();
				if(inside.contains(cell.getNeighbor(direction))){
					directions.add(direction);
				}
			}
			AtlasDirections direction = (AtlasDirections)directions.toArray()[rng.next(directions.size())];
			cell.setConnection(direction,true);
			cell.getNeighbor(direction).setConnection(direction.opposite(),true);
			for(AtlasDirections direction2:AtlasDirections.getDirections()){
				if(cell.getNeighbor(direction2)!=null && !frontier.contains(cell.getNeighbor(direction2)) && !inside.contains(cell.getNeighbor(direction2))){
					frontier.add(cell.getNeighbor(direction2));
				}
			}
		}
	}

	@Override
	public Atlas build(Configuration theConfiguration) {
		Atlas atlas = new Atlas(theConfiguration);
		Integer columns = theConfiguration.getPropertyGroups().get(PropertyGroupConstants.GAME_CONSTANTS).<Integer>get(GameConstants.ATLAS_COLUMNS);
		AtlasColumnBuilder builder = theConfiguration.getPropertyGroups().get(PropertyGroupConstants.BUILDERS).<AtlasColumnBuilder>get(BuilderConstants.ATLAS_COLUMN_BUILDER);
		for(Integer column=0;column<columns;++column){
			atlas.put(column, builder.build(atlas));
		}
		populateNeighbors(atlas);
		generateMaze(atlas);
		generateRooms(atlas);
		populateItems(atlas);
		populateCreatures(atlas);
		return atlas;
	}

	private void populateCreatures(Atlas atlas) {
		// TODO Auto-generated method stub
		
	}

	private void populateItems(Atlas atlas) {
		// TODO Auto-generated method stub
		
	}
	
	private void generateRoom(Layer layer){
		Configuration configuration = layer.getConfiguration();
		DescriptorTable propertyGroups = configuration.getPropertyGroups();
		RandomNumberGenerator rng=propertyGroups.get(PropertyGroupConstants.UTILITIES).<RandomNumberGenerator>get(UtilitiesConstants.RANDOM_NUMBER_GENERATOR);
		Boolean chamber = propertyGroups.
				get(PropertyGroupConstants.GENERATORS).
				<WeightedGenerator<Boolean>>get(
						String.format(GeneratorConstants.CHAMBER_GENERATOR_FORMAT, 
								layer.getConnectionCount())).
								generate(rng);
		MapManager mapManager = configuration.getMaps();
		DescriptorTable terrains = configuration.getTerrains();
		Iterator<String> iterator = terrains.keySet().iterator();
		Map<Integer,Descriptor> cqmTable = new HashMap<Integer,Descriptor>();
		while(iterator.hasNext()){
			String terrainIdentifier = iterator.next();
			Descriptor descriptor = terrains.get(terrainIdentifier);
			Integer cqmValue = descriptor.<Integer>get(TerrainConstants.CQM_VALUE);
			cqmTable.put(cqmValue,descriptor);
		}
		if(chamber){
			//draw the base chamber
			drawCQM(layer,mapManager.get(MAP_CHAMBER_BASE),cqmTable);
			Boolean deadEnd = (layer.getConnectionCount()==1);
			if(deadEnd){
				drawCQM(layer,mapManager.get(MAP_CHAMBER_DEAD_END),cqmTable);
			}
			//draw chamber detail
			drawCQM(layer,mapManager.get(propertyGroups.get(PropertyGroupConstants.GENERATORS).<WeightedGenerator<String>>get(GeneratorConstants.CHAMBER_DETAIL).generate(rng)),cqmTable);
			//draw north door
			if(layer.getConnection(AtlasDirections.NORTH)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.NORTH);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_CHAMBER_DOOR_NORTH),cqmTable);
				}else{
					drawCQM(layer,mapManager.get(MAP_CHAMBER_OPEN_NORTH),cqmTable);
				}
			}
			//draw east door
			if(layer.getConnection(AtlasDirections.EAST)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.EAST);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_CHAMBER_DOOR_EAST),cqmTable);
				}else{
					drawCQM(layer,mapManager.get(MAP_CHAMBER_OPEN_EAST),cqmTable);
				}
			}
			//draw south door
			if(layer.getConnection(AtlasDirections.SOUTH)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.SOUTH);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_CHAMBER_DOOR_SOUTH),cqmTable);
				}else{
					drawCQM(layer,mapManager.get(MAP_CHAMBER_OPEN_SOUTH),cqmTable);
				}
			}
			//draw west door
			if(layer.getConnection(AtlasDirections.WEST)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.WEST);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_CHAMBER_DOOR_WEST),cqmTable);
				}else{
					drawCQM(layer,mapManager.get(MAP_CHAMBER_OPEN_WEST),cqmTable);
				}
			}
		}else{
			//determine passageway configuration
			Integer value = 0;
			//north
			if(layer.getConnection(AtlasDirections.NORTH)){
				value+=1;
			}
			//east
			if(layer.getConnection(AtlasDirections.EAST)){
				value+=2;
			}
			//south
			if(layer.getConnection(AtlasDirections.SOUTH)){
				value+=4;
			}
			//west
			if(layer.getConnection(AtlasDirections.WEST)){
				value+=8;
			}
			//draw passageway map
			drawCQM(layer,mapManager.get(String.format(MAP_PWAY_FORMAT,value)),cqmTable);
			//draw north door if needed
			if(layer.getConnection(AtlasDirections.NORTH)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.NORTH);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_PWAY_DOOR_NORTH),cqmTable);
				}
			}
			//draw east door if needed
			if(layer.getConnection(AtlasDirections.EAST)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.EAST);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_PWAY_DOOR_EAST),cqmTable);
				}
			}
			//draw south door if needed
			if(layer.getConnection(AtlasDirections.SOUTH)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.SOUTH);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_PWAY_DOOR_SOUTH),cqmTable);
				}
			}
			//draw west door if needed
			if(layer.getConnection(AtlasDirections.WEST)){
				Layer neighborLayer = layer.getNeighbor(AtlasDirections.WEST);
				if(layer.getConnectionCount()==1 || neighborLayer.getConnectionCount()==1){
					drawCQM(layer,mapManager.get(MAP_PWAY_DOOR_WEST),cqmTable);
				}
			}
		}
	}

	private void drawCQM(Layer layer, CQMFile cqmFile,
			Map<Integer, Descriptor> cqmTable) {
		Iterator<Integer> columnIterator = layer.keySet().iterator();
		while(columnIterator.hasNext()){
			Integer column = columnIterator.next();
			Iterator<Integer> rowIterator = layer.get(column).keySet().iterator();
			while(rowIterator.hasNext()){
				Integer row = rowIterator.next();
				Integer cqmValue = (int) cqmFile.getCell(column, row);
				if(cqmTable.containsKey(cqmValue)){
					layer.get(column).get(row).setTerrain(new BaseTerrainInstance(layer.get(column).get(row),cqmTable.get(cqmValue)));
				}
			}
		}
		
	}

	private void generateRooms(Atlas atlas) {
		Iterator<Integer> columnIterator = atlas.keySet().iterator();
		while(columnIterator.hasNext()){
			Integer column = columnIterator.next();
			Iterator<Integer> rowIterator = atlas.get(column).keySet().iterator();
			while(rowIterator.hasNext()){
				Integer row = rowIterator.next();
				Layer cell = atlas.get(column).get(row).get(0);
				generateRoom(cell);
			}
		}
	}

}
